#include <windows.h>
#include <GL/glew.h>
#include <GL/glut.h>
#define _USE_MATH_DEFINES
#include <cmath>
#include <iostream>
#include "camera.h"

#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "opengl32.lib")

camera_t	camera(6.0f);			// Camera object
int			w, h;			// Width and height of the viewport. Used to correctly setup projection matrix.
vector4		camera_pos;		// Position of the camera in world space
vector3		camera_up;
vector3     camera_dir;

float		time = 0.0f;	// Time in seconds elapsed since launching application

const float dodecahedron_r = sqrtf( 0.375f + 0.125f * sqrtf(5) );	// Radius of the circle inscribed in dodecahedron's face (assuming that circle circumscribed has unit radius)
const float dodecahedron_h = 1.0f + 2.0f * dodecahedron_r;			// Distance between two parallel faces of dodecahedron
const float dodecahedron_a = acosf(-0.2f*sqrtf(5.0f));				// angle (in radians) between two faces of dodecahedron


unsigned char color_table[12][3] = { // Table of colors to be used for dodecahedron's faces
	{253,198,137}, {255,247,153}, {196,223,155}, {162,211,156}, {130,202,156}, {122,204,200}, 
	{109,207,246}, {125,167,216}, {131,147,202}, {135,129,189}, {161,134,190}, {244,154,193} };

matrix44 matView;					
matrix44 matDodecahedron[12];
GLuint elipseDl;

// functions

void set_camera()
// Setup projection and view matrices, matView, camera_pos, enable culling
{
	camera.setProjection(60.0f, (float)w, (float)h, 0.1f, 1000.0f);
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glMatrixMode(GL_PROJECTION);
	matrix44 mProj = camera.getProjectionMatrix();
	glLoadMatrixf((float*)(&mProj));
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glLoadMatrixf((float*)(&(camera.getViewMatrix())));
}

void set_light0()
// Setup one positional light positioned at the camera.
// Remember: positions of lights are transformed by currently set GL_MODELVIEW matrix
{
	GLfloat ambientLight[] = {0.3f, 0.3f, 0.3f, 1.0f};
	GLfloat diffuseLight[] = {0.7f, 0.7f, 0.7f, 1.0f};
	GLfloat specularLight[] = {0.2f, 0.2f, 0.2f, 1.0f};
	GLfloat lightPos[] = {camera_pos.x, camera_pos.y, camera_pos.z, 0};
	glEnable(GL_LIGHTING);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glEnable(GL_LIGHT0);

	//glDisable(GL_LIGHT1);
	//glDisable(GL_LIGHT2);
}


void set_light1()
// Setup one positional light positioned at the camera.
// Setup two additional green and blue lights.
// Remember: positions of lights are transformed by currently set GL_MODELVIEW matrix
{
	GLfloat ambientLight[] = {0.1f, 0.1f, 0.1f, 1.0f};
	GLfloat diffuseLight[] = {0.3f, 0.3f, 0.3f, 1.0f};
	GLfloat specularLight[] = {0.2f, 0.2f, 0.2f, 1.0f};
	GLfloat lightPos[] = {camera_pos.x, camera_pos.y, camera_pos.z, 0};
	glEnable(GL_LIGHTING);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glEnable(GL_LIGHT0);

	vector4 zero(0.0f,0.0f,0.0f,0.0f);
	vector4 green(0.0f,1.0f,0.0f,1.0f);
	vector4 blue(0.0f,0.0f,1.0f,1.0f);

	// TODO: add green and blue lights
}

void pentagon()
// Draw pentagon in XY plane
{
	glBegin(GL_TRIANGLE_FAN);
	glNormal3f(0.0f,0.0f,1.0f);
	for (int i = 0; i < 5; ++i)
	{
		float angle = DegToRad(72.0f*static_cast<float>(i));
		glVertex3f( cosf(angle), sinf(angle), 0.0f );
	}
	glEnd();
}

void init_dodecahedron()
// Compute matDodecahedron
{
	matDodecahedron[0] = TranslateMatrix44(0.0f, 0.0f, dodecahedron_h/2.0f);// * TranslateMatrix44(0.0, 0.0, -m_fDistance)
	for(int i=1; i< 6; ++i)
	{
		matDodecahedron[i] = RotateRadMatrix44('z', DegToRad(72.0f * (i-1) + 36.0f))*
			RotateRadMatrix44('y', (float)M_PI - dodecahedron_a) *
			matDodecahedron[0];
	}

	matDodecahedron[6] = TranslateMatrix44(0.0f, 0.0f, -dodecahedron_h/2.0f) * RotateRadMatrix44('y', (float)M_PI);
	for(int i=7; i< 12; ++i)
	{
		matDodecahedron[i] = RotateRadMatrix44('z', DegToRad(72.0f * (i-1) + 36.0f))*
			RotateRadMatrix44('y', (float)M_PI - dodecahedron_a) *
			matDodecahedron[6];
	}
}

void dodecahedron(bool colors)
// If colors is TRUE than draw faces with corresponding colors and semi-transparent with alpha = 100.
// If colors is FALSE (which happens during rendering dodecahedron in mirror) turn off blending and use default (white) color.
{
	for(int i=0; i<12; ++i)
	{
		glPushMatrix();
		glMultMatrixf((GLfloat *)(matDodecahedron[i].col));
		glColor3ub(color_table[i][0], color_table[i][1], color_table[i][2]);
		pentagon();
		glPopMatrix();
	}
}

void drawElipse(float a, float b, int verticesCount, float width)
{
	float x,y,z = width/2.0f;
	float step = M_PI * 2 / verticesCount;
	float angle = 0.0f;
	glBegin(GL_QUAD_STRIP);

	for(int i = 0; i< verticesCount; ++i)
	{
		x  = a * sinf(angle);
		y = b* cos(angle);
		glVertex3f(x, y, z);
		glVertex3f(x, y, -z);
		angle += step;
	}
	x  = a * sinf(0.0f);
	y = b* cos(0.0f);
	glVertex3f(x, y, z);
	glVertex3f(x, y, -z);

	glEnd();
}

void initElipse(float a, float b, int verticesCount = 3, float width = 2.0f)
{
	// Create the id for the list
	elipseDl = glGenLists(1);

	// start list
	glNewList(elipseDl,GL_COMPILE);

	// call the function that contains the rendering commands
	drawElipse(a, b, verticesCount, width);

	// endList
	glEndList();

	return;
}



void gl_vertex_normal(vector3 vertex, vector3 normal, bool revert_normal)
// Just a helper function
{
	if (revert_normal)
		glNormal3f( -normal.x, -normal.y, -normal.z );
	else
		glNormal3f( normal.x, normal.y, normal.z );
	glVertex3f( vertex.x, vertex.y, vertex.z );

}


void display()
{
	// Clear all the buffers used
	glClearColor(1.0f,1.0f,1.0f,1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	// Setup transformation matrices for actual camera position and orientation
	set_camera();

	// White color - default for butterfly and dodecahedron rendered in mirror
	glColor3f(1,1,1);
	
	// Render dodecahedron with only one white light
	set_light0();//set_light1();
	dodecahedron(true);
	glCallList(elipseDl);

	glutSwapBuffers();
}

void idle()
// Called by GLUT in a loop
{
	DWORD time = GetTickCount();
	time = static_cast<float>(time)/1000.0f;
	glutPostRedisplay();
}

void reshape(int _w, int _h)
// Called by GLUT when the size of the window changes
{
	w = _w;
	h = _h;
	glViewport(0,0,w,h);
}

void mouse(int button, int state, int x, int y)
// Called by GLUT when mouse buttons state changes 
{
	switch(button)
	{
	case GLUT_LEFT_BUTTON:
		if(state == GLUT_DOWN)
		{
			camera.mouseProc(camera_t::LBUTTONDOWN, x, y);
		}
		else
		{
			camera.mouseProc(camera_t::LBUTTONUP, x, y);
		}
		break;
	case GLUT_RIGHT_BUTTON:
		if(state == GLUT_DOWN)
		{
			camera.mouseProc(camera_t::RBUTTONDOWN, x, y);
		}
		else
		{
			camera.mouseProc(camera_t::RBUTTONUP, x, y);
		}
		break;
	}
	glutPostRedisplay();
}

void mousemove(int x, int y)
// Called by GLUT when mouse moves
{
	camera.mouseProc(camera_t::MOUSEMOVE, x, y);
	glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
// Called by GLUT when key is pressed
{
	static int wall = 0;
	vector3 pos;
	matrix44 view;

	switch (key)
	{
		case ' ':
			glutPostRedisplay();
			break;
		case 27:
		case 'q':
		case 'Q':
         exit(0);
		case 'w':
		case 'W':
			pos = vector3(matDodecahedron[wall] * vector4(0.0f, 0.0f, 0.0f, 1.0f));
			wall = (wall+1)%12;
			
			camera.setDirection(0.0f, 0.0f);
			camera.setViewMatrix(pos, vector3(0.0, 1.0, 0.0), vector3(0.0, 0.0, 0.0));
			glutPostRedisplay();
			break;
		case 's':
		case 'S':

			break;
         break;
	}
}

void init()
// Initialization of:
// - dodecahedron matrices
// Default material properties.
{
	init_dodecahedron();
	initElipse(2.0f, 4.0, 512);

	GLfloat ambiref[] = {0.3f, 0.3f, 0.3f, 1.0f};
	GLfloat diffref[] = {0.5f, 0.5f, 0.5f, 1.0f};
	GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambiref);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffref);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specref);
	glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, 128);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_NORMALIZE);
}

void done()
// Delete OpenGL objects on exit
{
	
}

int main(int argc, char **argv)
{
	glewInit();
	glutInit(&argc, argv);
	glutInitWindowSize(800, 800);
	glutInitDisplayString("alpha>=8 rgb stencil>=8 double depth>=24");	// Notice the "stencil>=8" for stencil buffer allocation - that is not the default
	glutCreateWindow("Butterfly");
	glutDisplayFunc(display);
	glutMouseFunc(mouse);
	glutMotionFunc(mousemove);
	glutKeyboardFunc(keyboard);
	glutReshapeFunc(reshape);
	glutIdleFunc(idle);
	init();
	glutMainLoop();
	done();
	return 0;
}
